package ups.MapParse;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.imageio.ImageIO;

public class TextFinder {

	private ArrayList<PositionedString> positionedStrings;

	public TextFinder( File imageFile, ArrayList<LetterPattern> patterns, ArrayList<String>possibleWords ) throws Exception
	{
		/* Load Image From File */
		BufferedImage image = _loadImage( imageFile );
		/* Convert to faster-access array */
		int[] imageBitmapArray = new int[ image.getWidth() * image.getHeight() ]; 
		image.getRGB( 0, 0, image.getWidth(), image.getHeight(), imageBitmapArray, 0, image.getWidth() );
		
		/* Need to Learn the Possible Words */
		Image mapImage = new Image( imageBitmapArray, new Size( image.getWidth(), image.getHeight() ));
		ShipNameFinder nameFinder = new ShipNameFinder( mapImage );
		//System.out.println("Found Possible Words:");
		System.out.println( nameFinder.getShipNames().toString() );
		//System.out.println("End Possible Words");
		if( nameFinder.getShipNames().size() != 0 ) {
			possibleWords = nameFinder.getShipNames();
		}
		
		/* Some storage for our initial and final results */
		ArrayList<PositionedLetter> matches = new ArrayList<PositionedLetter>();
		this.positionedStrings = new ArrayList<PositionedString>();

		/* Initial Pass To Find Arrows */
		ArrowFinder find = new ArrowFinder( imageBitmapArray, image.getWidth(), image.getHeight() );		
		ArrayList<Point> arrows = find.getArrowLocations();
		
		/* Initial Pass to find areas that might contain letters */
		TreeSet<Point> possiblePoints = possiblePointSearch( imageBitmapArray, image.getWidth(), image.getHeight() );
		
		/* Some loop variables */
		int X, Y, W, P;
		int maximumPatternWidth = patterns.get( patterns.size() - 1 ).getWidth();
		int minimumPatternWidth = patterns.get( 0 ).getWidth();
		
		/* Data structures for caching of Pixel and BinaryTable objects (so we only make the minimum of each) */
		BinaryTable[][] tables = _buildTables( patterns );		
		PixelCache pixelCache = new PixelCache();
		
		/* Main Loop: Finds patterns (letters) within data */
		for( Point point : possiblePoints ) {
			X = point.x;
			Y = point.y;
			for( W = maximumPatternWidth; W >= minimumPatternWidth && ( (X + W - 1) < image.getWidth() ); W-- ) {
				for( P = 0; P < patterns.size(); P++ ) {
					LetterPattern pattern = patterns.get(P);
					if( pattern.getHeight() + Y < image.getHeight() && pattern.getWidth() == W ) {
						boolean[][] table = tables[W][pattern.getHeight()].table;
						LetterPattern newSample = new LetterPattern( X, Y, W, pattern.getHeight(), imageBitmapArray, image.getWidth(), table, pixelCache );						
						if( pattern.compare( newSample ) ) {
							PositionedLetter posLetter =  new PositionedLetter( pattern.getName(), new Point( X, Y + pattern.getOffset() ), newSample.getColor());
							posLetter.setWidth( pattern.getWidth() );
							matches.add( posLetter );
							_writePixelsDirect( imageBitmapArray, image.getWidth(), X, Y, pattern.getWidth(), pattern.getHeight(), Pixel.whitePixel );
							P = patterns.size();
						}
					}
				}
			}
		}
		
		/* If this number is ever 256 we need to up the maximum */
		//System.out.println( "Used " + pixelCache.length + " colors.");

		/* Array "matches" Now Contains the Letters and their Positions */
		Collections.sort( matches, new PositionedLetterComparator());
		
		/* Consolidates individual PositionedLetters into PositionedStrings */
		StringBuffer currentString = new StringBuffer();
		int prevY = 0;
		int prevX = 0;
	
		int currentStringMinX = 0;
		int currentStringMinY = 0;

		int currentStringMaxX = 0;
		int currentStringMaxY = 0;
		
		int lastLetterWidth = 0; // HACK TO KEEP LETTER WIDTH
		
		Pixel color = null;
		
		Point nextPoint = new Point(0,0);
		for( PositionedLetter let : matches ) {
			nextPoint 		= let.getLocation();
			lastLetterWidth = let.getWidth();
			if( ( prevY != nextPoint.y || prevX + 20 <= nextPoint.x ) && currentString.length() >	 0) {
				currentStringMaxX = prevX + let.getWidth();
				currentStringMaxY = prevY + 7; // 7 is the number of pixels high a word is
				positionedStrings.add( new PositionedString( currentString.toString(), new Point( (currentStringMinX + currentStringMaxX) / 2,  (currentStringMinY + currentStringMaxY) / 2), color ));
				currentString = new StringBuffer();
			}
			if( currentString.length() == 0 ) {
				currentStringMinX = nextPoint.x;
				currentStringMinY = nextPoint.y;				
				}

				color = let.getColor();
				currentString.append( let.getCharacter() );
				prevY = nextPoint.y;
				prevX = nextPoint.x;
		}
		
		/* Need to Clean Up Dangling String */
		if( currentString.length() != 0 ) {
			positionedStrings.add( new PositionedString( currentString.toString(), new Point( (currentStringMinX + nextPoint.x + lastLetterWidth) / 2,  (currentStringMinY + nextPoint.y + 7) / 2), color ));
		}
		
		System.out.println( "Positioned Strings From the Parser { ");
		for( PositionedString pStr : positionedStrings ) {
			System.out.println( "\t" + pStr.toString() );
		}
		System.out.println( "}");
		
		/* Add the arrows into the positionedStrings */
		for( Point arrowLoc : arrows ) {
			positionedStrings.add( new PositionedString( "1", arrowLoc, Pixel.bluePixel ));
		}
		
	//	System.out.println( positionedStrings.toString() );
		
		ArrayList<PositionedString> withoutShips = new ArrayList<PositionedString>();
		ArrayList<PositionedString> removers = new ArrayList<PositionedString>();
		for( PositionedString guess : positionedStrings) {
			// A single 0 means a ship marker 
			// A 1 means a moving ship
			if( guess.getString().equals( "0" ) || guess.getString().equals("1")) {
				
			} else if( guess.getString().length() <= 2) {
				removers.add( guess );
				//System.out.println("Found a marker");
			} else {
				withoutShips.add( guess );
			}
		}
		positionedStrings.removeAll( removers );
		
		/* Make this into a lookup */
		//if( withoutShips.size() != possibleWords.size()) {
		//	throw( new Exception("Too many ships in this map"));
		//}

		
		SpellCheck sp = new SpellCheck( withoutShips , possibleWords );		
		sp.apply();
		
	}
	
	public ArrayList<PositionedString> positionedStrings() {
		//for( PositionedString  s : positionedStrings ) {
			//System.out.println( s );
		//}
		return positionedStrings;
	}
	
	public TreeSet<Point> possiblePointSearch( int[] imageData, int width, int height ) {
		int i,j;
		
		TreeSet<Point> sortedPoints = new TreeSet<Point>( new PointComparator() );
		
		for( j = 0; j < height; j++) {
			for( i = 0; i < width; i++ ) {
				int value = imageData[ j * width + i];
				
				if( value == Pixel.bluePixel.intValue || value == Pixel.redPixel.intValue ) {
					sortedPoints.add( new Point( i, j ));
					if( i >= 1 ) {
						sortedPoints.add( new Point( i - 1, j ));
					}
					if( j >= 1 ) {
						sortedPoints.add( new Point( i, j - 1 ));
					}
					if( i >= 1 && j >= 1) {
						sortedPoints.add( new Point( i - 1, j - 1 ));
					}
				}
			}
		}
		
		return sortedPoints;
	}
	
	/* Private Methods */
	private static BufferedImage _loadImage( File imageFile ) throws Exception {
		BufferedImage image = null;
		try {
			image = ImageIO.read( imageFile );
		} catch ( IOException e ) {
			System.out.println( "Failed Loading Image: " + e.toString() +  " File: " +  imageFile.toString() );
			throw( e );
		} catch ( Exception ioex ) {
			System.out.println(  "Failed Loading Image " + ioex.toString() + " File: " +  imageFile.toString() );
			throw( ioex );
		}
		
		return image;
	}
	
	private static BinaryTable[][] _buildTables( ArrayList<LetterPattern> list) {
		int maxWidth = 0;
		int maxHeight = 0;
		for( LetterPattern pat : list) {
			if( pat.getWidth() > maxWidth ) {
				maxWidth = pat.getWidth();
			}
			if( pat.getHeight() > maxHeight) {
				maxHeight = pat.getHeight();
			}
		}
			
		BinaryTable[][] table = new BinaryTable[maxWidth+1][maxHeight+1];
		for( LetterPattern pat : list) {
			table[ pat.getWidth()][pat.getHeight()] = new BinaryTable( pat.getWidth(), pat.getHeight() );
		}
		
		return table;
	}
	
	private static void _writePixelsDirect( int[] imageArray, int strideWidth, int x, int y, int width, int height, Pixel color ) {
		int i, j;
		for( j = y; j < y + height; j++ ) {
			for( i = x; i < x + width; i++ ) {
				imageArray[ j * strideWidth + i ] = color.intValue;
			}
		}
	}
	
	private String _closestMatch(String match, ArrayList<String> possibilites)
	{
		DistanceCalculator calc = new DistanceCalculator();
		
		int lowestDistance = Integer.MAX_VALUE;
		String bestMatch = null;
		for( String possible : possibilites ) {
			int distance = calc.distance2( match, possible);
			if( distance < lowestDistance ) {
				lowestDistance = distance;
				bestMatch = possible;
			}
		}
		if( bestMatch == null  || lowestDistance > 3 ) {
			return null;
		} else {
			return bestMatch;
		}
		
	}
}
